package cn.xorange.commons.security.filter;

import cn.xorange.commons.security.constants.HeaderConstants;
import cn.xorange.commons.security.constants.SecurityConstants;
import cn.xorange.commons.security.entity.ClaimsInfo;
import cn.xorange.commons.security.entity.SecurityLoginResult;
import cn.xorange.commons.security.entity.SecurityUser;
import cn.xorange.commons.security.enums.SysType;
import cn.xorange.commons.security.exception.SecurityErrorCode;
import cn.xorange.commons.security.exception.SecurityException;
import cn.xorange.commons.security.service.LoginHandler;
import cn.xorange.commons.security.service.LoginHandlerFactory;
import cn.xorange.commons.security.token.CustomAuthToken;
import cn.xorange.commons.security.utils.JwtUtils;
import cn.xorange.commons.utils.async.AsyncManager;
import cn.xorange.commons.utils.cache.ICacheService;
import cn.xorange.commons.utils.captcha.Captcha;
import cn.xorange.commons.utils.http.ServletUtils;
import cn.xorange.commons.utils.id.IdUtils;
import cn.xorange.commons.utils.lang.ObjectUtils;
import cn.xorange.commons.utils.spring.SpringUtils;
import com.alibaba.fastjson2.JSONObject;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * @author : yangjian
 * date : 2023/11/25
 */
@Slf4j
public class LoginFilter extends AbstractAuthenticationProcessingFilter {

    public LoginFilter(String pathUrl, LoginFilterService loginFilterService, LoginHandlerFactory loginHandlerFactory) {
        super(new AntPathRequestMatcher(pathUrl + "**","POST"));
        this.loginFilterService = loginFilterService;
        this.loginHandlerFactory = loginHandlerFactory;
    }

    LoginHandlerFactory loginHandlerFactory;
    public LoginFilterService loginFilterService;


    /**
     * SysLoginFilter -> SysAuthFilter -> SysToken->SysUserPasswordAuthProvider->SysUserPasswordDetailService->AuthSuccessHandler
     */
    @SneakyThrows
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        String sysType = request.getHeader(HeaderConstants.X_SYS_TYPE);
        String source = request.getHeader(HeaderConstants.X_SOURCE);

        JSONObject object = ServletUtils.getParamJsonObj(request);
        Long tenantId = ObjectUtils.toLong(object.getString(SecurityConstants.USER_TENANT_ID));
        String loginName = object.getString(SecurityConstants.LOGIN_NAME);
        String loginType = object.getString(SecurityConstants.LOGIN_TYPE);


        // 接口登录 不传x-sys-type
        if(SysType.API.type().equals(loginType)){
            sysType = SysType.API.type();
        }

        if (Objects.isNull(source) || source.isEmpty() || Objects.isNull(sysType) || Objects.isNull(loginType) || Objects.isNull(loginName)){
            throw new SecurityException(SecurityErrorCode.PARAM_ERROR);
        }
        LoginHandler loginHandler = this.loginFilterService.getLoginHandler(sysType,loginType,loginHandlerFactory);

        this.loginFilterService.checkLoginErr(response,source,loginName,loginHandler);

        //验证码校验
        boolean enableCaptcha = this.loginFilterService.enableCaptcha(loginHandler);
        if(enableCaptcha){
            String code = object.getString(SecurityConstants.LOGIN_CODE);
            String uuid = object.getString(SecurityConstants.LOGIN_UUID);
            Captcha captcha = this.loginFilterService.checkCaptcha(code,uuid);
            if(!SecurityConstants.FLAG_SUCCESS.equals(String.valueOf(captcha.getSysCode()))){
                throw  new SecurityException(captcha.getStatus(), captcha.getSysCode(),captcha.getMsg());
            }
        }

        // 判断是否是超级用户
        String superType = loginFilterService.checkSuperUserLogin(loginName);
        object.put(SecurityConstants.USER_IS_SUPPER, superType);
        object.put(HeaderConstants.X_SYS_TYPE,sysType);
        object.put(HeaderConstants.X_SOURCE, source);
        object.put(SecurityConstants.LOGIN_TYPE,loginType);


        request.setAttribute(HeaderConstants.X_SYS_TYPE, sysType);
        request.setAttribute(HeaderConstants.X_SOURCE, source);
        request.setAttribute(SecurityConstants.LOGIN_TYPE, loginType);
        request.setAttribute(SecurityConstants.LOGIN_NAME, loginName);
        request.setAttribute(SecurityConstants.USER_TENANT_ID, tenantId);

        // 2. 封装成 Token 调用 AuthenticationManager 的 authenticated 方法，该方法中根据 Token 的类型去调用对应 Provider 的 authenticated
        CustomAuthToken token = new CustomAuthToken(loginName,object);

        // 3. 返回 authenticated 方法的返回值
        return this.getAuthenticationManager().authenticate(token);
    }



    /**
     * 登录成功
     */
    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication auth) {

        String sysType =  String.valueOf(request.getAttribute(HeaderConstants.X_SYS_TYPE));
        String source = String.valueOf(request.getAttribute(HeaderConstants.X_SOURCE));

        String loginType = String.valueOf(request.getAttribute(SecurityConstants.LOGIN_TYPE));
        String loginName =  String.valueOf(request.getAttribute(SecurityConstants.LOGIN_NAME));


        LoginHandler loginHandler = loginFilterService.getLoginHandler(sysType, loginType ,loginHandlerFactory);
        SecurityUser user = (SecurityUser)auth.getDetails();
        String jwtToken = "";
        if(Objects.nonNull(user.getUserId())){
            String tokenId = IdUtils.fastSimpleUUID();
            jwtToken = JwtUtils.createJwtToken(sysType, loginType, source, tokenId , loginHandler.config().getTokenExpire());
            //存入缓存
            ClaimsInfo claimsInfo = new ClaimsInfo(sysType, loginType, source, tokenId);
            SpringUtils.getBean(ICacheService.class).setCacheObject(claimsInfo.tokenKey(), user ,loginHandler.config().getTokenExpire(), TimeUnit.MINUTES);
            // 登录成功，清除用户登录失败缓存
            loginFilterService.securityService.delLoginErr(source,loginName);
        }

        SecurityLoginResult loginResult = new SecurityLoginResult();
        loginResult.setAccessToken(jwtToken);
        loginResult.setExpiresIn(loginHandler.config().getTokenExpire());

        // 记录日志
        this.saveLog(loginHandler,loginName,user,0,"成功");
        ServletUtils.responseSuccessOut(response,loginResult);

    }
    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) {

        String sysType =  String.valueOf(request.getAttribute(HeaderConstants.X_SYS_TYPE));
        String source = String.valueOf(request.getAttribute(HeaderConstants.X_SOURCE));
        String loginType = String.valueOf(request.getAttribute(SecurityConstants.LOGIN_TYPE));
        String loginName = String.valueOf(request.getAttribute(SecurityConstants.LOGIN_NAME));

        LoginHandler loginHandler = loginFilterService.getLoginHandler(sysType, loginType ,loginHandlerFactory);
        if(authException instanceof SecurityException){
            SecurityException e = (SecurityException)authException;
            if(SecurityErrorCode.AUTH_USERNAME_OR_PWD_ERROR.getCode() == e.getCode()){
                loginFilterService.securityService.setLoginErr(source, loginName, loginHandler.config().getClockTime());
                this.saveLog(loginHandler, loginName, null, 1, e.getMessage());
            }

            ServletUtils.responseOut(response,e.getStatus(),1,e.getMessage(),e.getData());
        }else {
            log.error(authException.getMessage());
            ServletUtils.responseOut(response,500,500,authException.getMessage(),null);
        }

    }

    private void saveLog(LoginHandler loginHandler,String loginName, SecurityUser user, Integer status, String msg){
        AsyncManager.me().execute(loginHandler.setLog(loginName,user,status,msg));
    }


}
